package com.songyang.yougnsql.core.version;

import com.songyang.yougnsql.common.ErrorMsg;
import com.songyang.yougnsql.common.YoungSQLException;
import com.songyang.yougnsql.core.transaction.TransactionManager;

/**
 * @ClassName Visibility
 * @Description mvcc 可见性
 * @date 2022/12/16 10:47
 * @Author yanceysong
 * @Version 1.0
 */
public class Visibility {
    /**
     * 根据不同的事务隔离级别判断当前版本是否对某个事物可见
     *
     * @param tm          事务管理
     * @param transaction 内存中的事务
     * @param entity      版本的记录
     * @return 是否可见
     * @throws YoungSQLException 未知的事务隔离级别
     */
    public static boolean isVisible(TransactionManager tm, Transaction transaction, VersionEntry entity) throws YoungSQLException {
        if (transaction.getLevel() == Transaction.READ_COMMITTED) {
            return readCommitted(tm, transaction, entity);
        } else if (transaction.getLevel() == Transaction.REPEATABLE_READ) {
            return repeatableRead(tm, transaction, entity);
        } else if (transaction.getLevel() == Transaction.READ_UNCOMMITTED) {
            return readUnCommitted(tm, transaction, entity);
        } else if (transaction.getLevel() == Transaction.SERIALIZABLE) {
            return serialization(tm, transaction, entity);
        }
        throw new YoungSQLException(ErrorMsg.UNKNOWN_TRANSACTION_ISOLATION_LEVEL);
    }

    /**
     * 当是rc的事务隔离级别时，判断某个记录对事物是否可见
     *
     * @param tm          事务管理
     * @param transaction 内存中的事务
     * @param entity      记录
     * @return 结果
     */
    private static boolean readCommitted(TransactionManager tm, Transaction transaction, VersionEntry entity) throws YoungSQLException {
        long xid = transaction.getXid();
        long xMin = entity.getXMin();
        long xMax = entity.getXMax();
        /*
         * xmin 已经提交：(创建该事物的已经提交)
         *  a:xmax == 0 可以访问，因为这表示这个记录所对应的事务已经提交了并且没有被删除
         *  b xamx ！= 0(说明有事务在删除该数据)
         *      ⅰ xid == xmax 说明是当前事务在删除该数据 不能访问
         *      ⅱ xid ！= xmax 说明不是当前事务在删除该数据
         *          1 xmax 没有被提交 说明其它事务是正在删除或者取消了删除，那么可以访问
         *          2 xmax 被提交 说明被删除了 不能访问
         * xid 未提交：(创建该事物的没有提交)
         *  xid == xmin && xmax ==0 这代表这条记录就是当前事务创建的并且没有被当前事务删除 可以访问
         *  其他情况不可访问，因为xmin还没有提交，其他事务都不能够访问
         */
        if (xMin == xid && xMax == 0) return true;
        if (tm.isCommitted(xMin)) {
            //返回true的情况只有两种：1.事务已经提交并且没有被删除
            //                    2.事务已经提交并且删除的那个事务没有提交
            if (xMax == 0) return true;
            //不是自己删除判断删除的那个事务是否已经提交了
            if (xid != xMax) {
                return !tm.isCommitted(xMax);
            }
        }
        return false;
    }

    /**
     * 当是rr的事务隔离级别时，判断某个记录对事物是否可见
     * 即忽略：在本事务后开始的事务的数据
     * 本事务开始时还是 active 状态的事务的数据
     *
     * @param tm          事务管理
     * @param transaction 内存中的事务
     * @param entity      记录
     * @return 结果
     */
    private static boolean repeatableRead(TransactionManager tm, Transaction transaction, VersionEntry entity) throws YoungSQLException {
        long xid = transaction.getXid();
        long xMin = entity.getXMin();
        long xMax = entity.getXMax();
        /*
         * 1.xmin 未提交
         *   a xmid == xid && xmax ==0 min没有提交，是自己创建并且没有删除 可以访问
         *   b 其它情况不能访问
         * 2 xmin 已经提交，因为我们是根据文件判断该事务的状态，所以还需要根快照来决定是否可看  xmin提交、xmin < xid （当前事务所能访问的数据只能是在该事务开启之前存的）
         * !t.isInSnapshot(xmin) （当前事务开启的时候xmin不能处于活跃状态）这三个条件是必须满足的,如果这三个条件满足那么就是当前当前记录是在当前事务开启之前就已经提交了的
         *   a xmin == 0 代表没有被删 所以可以访问
         *   b xmax ！=0 (说明有事务正在删除该数据)
         *       ⅰ xid == xmax 不能被访问说明是当前事务删除了该数据
         *       ⅱ xid ！= xmax
         *           1 xmax未提交 可以访问
         *           2 xmax 已经提交
         *               a xid < xmax 说明这条数据是在xid之后被删除的所以可以访问
         *               b xid > xmax
         *                   ⅰ t.isInSnapshot(xmax) 说明在xid开启的时候，xmax还没有提交可以访问
         *                    ⅱ ！t.isInSnapshot(xmax) 不能访问
         */
        if (xMin == xid && xMax == 0) return true;
        if (tm.isCommitted(xMin) && xMin < xid && !transaction.isInSnapshot(xMin)) {
            if (xMax == 0) return true;
            if (xMax != xid) {
                return !tm.isCommitted(xMax) || xMax > xid || transaction.isInSnapshot(xMax);
            }
        }
        return false;
    }

    /**
     * 当是ru的事务隔离级别时，判断某个记录对事物是否可见
     *
     * @param tm          事务管理
     * @param transaction 内存中的事务
     * @param entity      记录
     * @return 结果
     */
    private static boolean readUnCommitted(TransactionManager tm, Transaction transaction, VersionEntry entity) {
        //todo
        return false;
    }

    /**
     * 当是串行化事务隔离级别时，判断某个记录对事物是否可见
     *
     * @param tm          事务管理
     * @param transaction 内存中的事务
     * @param entity      记录
     * @return 结果
     */
    private static boolean serialization(TransactionManager tm, Transaction transaction, VersionEntry entity) {
        //todo
        return false;
    }

    /**
     * 读提交是允许版本跳跃的，而可重复读则是不允许版本跳跃的。解决版本跳跃的思路也很简单：
     * 如果 Ti 需要修改 X，而 X 已经被 Ti 不可见的事务 Tj 修改了，那么要求 Ti 回滚。
     *
     * @param tm          事务管理
     * @param transaction 事务
     * @param entity      记录
     * @return 是否是跳跃表版本
     */
    public static boolean isVersionSkip(TransactionManager tm, Transaction transaction, VersionEntry entity) throws YoungSQLException {
        long xmax = entity.getXMax();
        if (transaction.getLevel() == Transaction.READ_COMMITTED) {
            return false;
        } else {
            return tm.isCommitted(xmax) && (xmax > transaction.getXid() || transaction.isInSnapshot(xmax));
        }
    }
}
